﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SocketAwaitable.cs" company="Rolosoft Ltd">
//   © Rolosoft Ltd
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

#region License

// Copyright 2013 Rolosoft Ltd
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion

namespace Rsft.Net.Dns.Extensions
{
    #region Usings

    using System;
    using System.Net.Sockets;
    using System.Runtime.CompilerServices;
    using System.Threading;
    using System.Threading.Tasks;

    #endregion

    /// <summary>
    ///     The socket awaitable.
    /// </summary>
    public sealed class SocketAwaitable : INotifyCompletion
    {
        #region Static Fields

        /// <summary>
        ///     The sentinel.
        /// </summary>
        private static readonly Action SENTINEL = () => { };

        #endregion

        #region Fields

        /// <summary>
        /// The continuation.
        /// </summary>
        internal Action Continuation;

        /// <summary>
        /// The event args.
        /// </summary>
        internal SocketAsyncEventArgs eventArgs;

        /// <summary>
        /// The was completed.
        /// </summary>
        internal bool wasCompleted;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="SocketAwaitable"/> class.
        /// </summary>
        /// <param name="eventArgs">
        /// The <see cref="SocketAsyncEventArgs"/> instance containing the event data.
        /// </param>
        /// <exception cref="System.ArgumentNullException">
        /// eventArgs
        /// </exception>
        public SocketAwaitable(SocketAsyncEventArgs eventArgs)
        {
            if (eventArgs == null)
            {
                throw new ArgumentNullException("eventArgs");
            }

            this.eventArgs = eventArgs;
            eventArgs.Completed += delegate
                {
                    var prev = this.Continuation
                               ?? Interlocked.CompareExchange(ref this.Continuation, SENTINEL, null);
                    if (prev != null)
                    {
                        prev();
                    }
                };
        }

        #endregion

        #region Public Properties

        /// <summary>
        ///     Gets a value indicating whether this instance is completed.
        /// </summary>
        /// <value>
        ///     <c>true</c> if this instance is completed; otherwise, <c>false</c>.
        /// </value>
        public bool IsCompleted
        {
            get
            {
                return this.wasCompleted;
            }
        }

        #endregion

        #region Public Methods and Operators

        /// <summary>
        ///     Gets the awaiter.
        /// </summary>
        /// <returns>
        ///     The <see cref="SocketAwaitable" />.
        /// </returns>
        public SocketAwaitable GetAwaiter()
        {
            return this;
        }

        /// <summary>
        ///     Gets the result.
        /// </summary>
        /// <exception cref="System.Net.Sockets.SocketException"></exception>
        public void GetResult()
        {
            if (this.eventArgs.SocketError != SocketError.Success)
            {
                throw new SocketException((int)this.eventArgs.SocketError);
            }
        }

        /// <summary>
        /// Schedules the continuation action that's invoked when the instance completes.
        /// </summary>
        /// <param name="continuation">
        /// The action to invoke when the operation completes.
        /// </param>
        public void OnCompleted(Action continuation)
        {
            if (this.Continuation == SENTINEL
                || Interlocked.CompareExchange(ref this.Continuation, continuation, null) == SENTINEL)
            {
                Task.Run(continuation);
            }
        }

        #endregion

        #region Methods

        /// <summary>
        ///     The reset.
        /// </summary>
        internal void Reset()
        {
            this.wasCompleted = false;
            this.Continuation = null;
        }

        #endregion
    }
}